import { useCallback, useEffect, useRef, useState } from "react";
import styles from "./index.module.less";

type HexColor = `#${string}`;
interface ScrollbarProps {
  children: React.ReactNode;
  scrollSize?: number;
  width: number;
  height: number;
  scrollBarColor?: HexColor;
}

const hexToRgb = (hex: HexColor): [number, number, number] => {
  hex = hex.replace(/^#/, "") as HexColor;
  if (hex.length === 3) {
    hex = hex.padEnd(6, hex[2]) as HexColor;
  }
  hex = hex.toUpperCase().padStart(6, "0") as HexColor;
  const r = parseInt(hex.slice(0, 2), 16);
  const g = parseInt(hex.slice(2, 4), 16);
  const b = parseInt(hex.slice(4, 6), 16);
  return [r, g, b];
};

const Scrollbar = (props: ScrollbarProps) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const contentRef = useRef<HTMLDivElement>(null);
  const [containerWidth, setContainerWidth] = useState(0);
  const [containerHeight, setContainerHeight] = useState(0);
  const [contentWidth, setContentWidth] = useState(0);
  const [contentHeight, setContentHeight] = useState(0);
  const [contentChildWidth, setContentChildWidth] = useState(0);
  const [contentChildHeight, setContentChildHeight] = useState(0);
  const [scrollSize, setScrollSize] = useState(0);
  const [yScrollHeight, setYScrollHeight] = useState(0);
  const [yScrollBarHeight, setYScrollBarHeight] = useState(0);
  const [xScrollWidth, setXScrollWidth] = useState(0);
  const [xScrollBarWidth, setXScrollBarWidth] = useState(0);
  const [scrollBarColor, setScrollBarColor] = useState<HexColor>("#000000");

  useEffect(() => {
    setScrollSize(!props.scrollSize ? 10 : props.scrollSize);
    setScrollBarColor(!props.scrollBarColor ? "#000000" : props.scrollBarColor);
    if (containerRef.current && contentRef.current) {
      const containerRefRect = containerRef.current.getBoundingClientRect();
      setContainerWidth(containerRefRect.width);
      setContainerHeight(containerRefRect.height);
      const contentRefChildRect = (
        contentRef.current?.firstElementChild as HTMLDivElement
      ).getBoundingClientRect();
      setContentChildWidth(contentRefChildRect.width);
      setContentChildHeight(contentRefChildRect.height);
      if (
        !(contentRef.current?.firstElementChild as HTMLDivElement).style
          .position
      ) {
        (
          contentRef.current?.firstElementChild as HTMLDivElement
        ).style.position = "absolute";
      }
      setContentWidth(
        contentChildHeight > containerHeight
          ? containerWidth - scrollSize
          : containerWidth
      );
      setContentHeight(
        contentChildWidth > containerWidth
          ? containerHeight - scrollSize
          : containerHeight
      );
      setYScrollHeight(
        contentChildWidth > containerWidth
          ? containerHeight - scrollSize
          : containerHeight
      );
      setYScrollBarHeight((contentHeight / contentChildHeight) * yScrollHeight);
      setXScrollWidth(
        contentChildHeight > containerHeight
          ? containerWidth - scrollSize
          : containerWidth
      );
      setXScrollBarWidth((contentWidth / contentChildWidth) * xScrollWidth);
    }
  }, [
    containerHeight,
    containerWidth,
    contentChildHeight,
    contentChildWidth,
    contentHeight,
    contentWidth,
    props.scrollBarColor,
    props.scrollSize,
    scrollSize,
    xScrollWidth,
    yScrollHeight,
  ]);

  const [yTop, setYTop] = useState(0);
  const yTopRef = useRef(0);
  const yScrollRef = useRef<HTMLDivElement>(null);
  const yMax = yScrollHeight - yScrollBarHeight;
  const yScrollClientY = useRef(0);
  const yMouseMoveHandle = useCallback(
    (e: MouseEvent) => {
      const yMove = e.clientY - yScrollClientY.current;
      yScrollClientY.current = e.clientY;
      yTopRef.current = yTopRef.current + yMove;
      if (yTopRef.current < 0) {
        yTopRef.current = 0;
      } else if (yTopRef.current > yMax) {
        yTopRef.current = yMax;
      }
      setYTop(yTopRef.current);
    },
    [yMax]
  );
  useEffect(() => {
    const yScroll = yScrollRef.current;
    const removeListeners = () => {
      window.removeEventListener("mousemove", yMouseMoveHandle);
      window.removeEventListener("mouseup", removeListeners);
    };
    if (yScroll) {
      yScroll.addEventListener("mousedown", (e) => {
        yScrollClientY.current = e.clientY;
        window.addEventListener("mousemove", yMouseMoveHandle);
        window.addEventListener("mouseup", removeListeners);
      });
    }
    return () => {
      window.removeEventListener("mousemove", removeListeners);
    };
  }, [yMouseMoveHandle]);

  const [xLeft, setXLeft] = useState(0);
  const xLeftRef = useRef(0);
  const xScrollRef = useRef<HTMLDivElement>(null);
  const xMax = xScrollWidth - xScrollBarWidth;
  const xScrollClientX = useRef(0);
  const xMouseMoveHandle = useCallback(
    (e: MouseEvent) => {
      const xMove = e.clientX - xScrollClientX.current;
      xScrollClientX.current = e.clientX;
      xLeftRef.current = xLeftRef.current + xMove;
      if (xLeftRef.current < 0) {
        xLeftRef.current = 0;
      } else if (xLeftRef.current > xMax) {
        xLeftRef.current = xMax;
      }
      setXLeft(xLeftRef.current);
    },
    [xMax]
  );
  useEffect(() => {
    const xScroll = xScrollRef.current;
    const removeListeners = () => {
      window.removeEventListener("mousemove", xMouseMoveHandle);
      window.removeEventListener("mouseup", removeListeners);
    };
    if (xScroll) {
      xScroll.addEventListener("mousedown", (e) => {
        xScrollClientX.current = e.clientX;
        window.addEventListener("mousemove", xMouseMoveHandle);
        window.addEventListener("mouseup", removeListeners);
      });
    }
    return () => {
      window.removeEventListener("mousemove", removeListeners);
    };
  }, [xMouseMoveHandle]);

  const keyHandle = useCallback(
    (e: KeyboardEvent) => {
      e.preventDefault();
      if (contentChildHeight > containerHeight) {
        if (e.key === "ArrowDown") {
          yTopRef.current =
            yTopRef.current + 10 > yMax ? yMax : yTopRef.current + 10;
        }
        if (e.key === "ArrowUp") {
          yTopRef.current = yTopRef.current - 10 < 0 ? 0 : yTopRef.current - 10;
        }
        setYTop(yTopRef.current);
      }
      if (contentChildWidth > containerWidth) {
        if (e.key === "ArrowRight") {
          xLeftRef.current =
            xLeftRef.current + 10 > xMax ? xMax : xLeftRef.current + 10;
        }
        if (e.key === "ArrowLeft") {
          xLeftRef.current =
            xLeftRef.current - 10 < 0 ? 0 : xLeftRef.current - 10;
        }
        setXLeft(xLeftRef.current);
      }
    },
    [
      containerHeight,
      containerWidth,
      contentChildHeight,
      contentChildWidth,
      xMax,
      yMax,
    ]
  );

  useEffect(() => {
    if (yScrollRef.current) {
      yScrollRef.current.style.top = `${yTop}px`;
      (contentRef.current?.firstElementChild as HTMLDivElement).style.top = `-${
        yTop * (contentChildHeight / contentHeight)
      }px`;
    }

    if (xScrollRef.current) {
      xScrollRef.current.style.left = `${xLeft}px`;
      (
        contentRef.current?.firstElementChild as HTMLDivElement
      ).style.left = `-${xLeft * (contentChildWidth / contentWidth)}px`;
    }
  }, [
    contentChildHeight,
    contentChildWidth,
    contentHeight,
    contentWidth,
    xLeft,
    yTop,
  ]);

  const wheelHandle = (e: WheelEvent) => {
    e.preventDefault();
    if (e.deltaY > 0) {
      if (contentChildHeight > containerHeight) {
        yTopRef.current =
          yTopRef.current + 10 > yMax ? yMax : yTopRef.current + 10;
      }
    } else {
      if (contentChildHeight > containerHeight) {
        yTopRef.current = yTopRef.current - 10 < 0 ? 0 : yTopRef.current - 10;
      }
    }
    setYTop(yTopRef.current);
  };

  return (
    <div
      className={styles.container}
      ref={containerRef}
      style={{ width: `${props.width}px`, height: `${props.height}px` }}
      onMouseOver={() => {
        window.addEventListener("keydown", keyHandle);
        containerRef.current?.addEventListener("wheel", wheelHandle);
      }}
      onMouseOut={() => {
        window.removeEventListener("keydown", keyHandle);
        containerRef.current?.removeEventListener("wheel", wheelHandle);
      }}
    >
      <div
        className={styles.content}
        ref={contentRef}
        style={{
          height: `${contentHeight}px`,
          width: `${contentWidth}px`,
        }}
      >
        {props.children}
      </div>
      {contentChildHeight > containerHeight ? (
        <div
          className={styles.yScroll}
          style={{
            height: `${yScrollHeight}px`,
            width: `${scrollSize}px`,
            backgroundColor: `rgba(${hexToRgb(scrollBarColor).join(",")}, 0.2)`,
          }}
        >
          <div
            ref={yScrollRef}
            className={styles.yScrollBar}
            style={{
              height: `${yScrollBarHeight}px`,
              width: `${scrollSize}px`,
              backgroundColor: `rgba(${hexToRgb(scrollBarColor).join(
                ","
              )}, 0.5)`,
            }}
          ></div>
        </div>
      ) : null}
      {contentChildWidth > containerWidth ? (
        <div
          className={styles.xScroll}
          style={{
            width: `${xScrollWidth}px`,
            height: `${scrollSize}px`,
            backgroundColor: `rgba(${hexToRgb(scrollBarColor).join(",")}, 0.2)`,
          }}
        >
          <div
            className={styles.xScrollBar}
            ref={xScrollRef}
            style={{
              width: `${xScrollBarWidth}px`,
              height: `${scrollSize}px`,
              backgroundColor: `rgba(${hexToRgb(scrollBarColor).join(
                ","
              )}, 0.5)`,
            }}
          ></div>
        </div>
      ) : null}
    </div>
  );
};

export default Scrollbar;
